/** 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.port;

import it.free.i.management.JmxFacilities;
import it.free.i.virtual.machine.hal.DeviceAliasRuntimeException;
import it.free.i.virtual.machine.hal.LinkingOperationException;
import it.free.i.virtual.machine.hal.devicealias.DeviceAliasFacilities;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommand;
import it.free.i.virtual.machine.hal.devicealias.command.DeviceAliasPortCommandVerb;
import it.free.i.virtual.machine.hal.devicealias.laboratory.positioningsystem.PositioningSystemDomainObjectFactory;
import it.free.i.virtual.machine.hal.devicealias.echo.CollisionEcho;
import it.free.i.virtual.machine.hal.devicealias.echo.PingEcho;
import it.free.i.virtual.machine.hal.devicealias.echo.PingEchoImpl;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.dynamicparameter.DynamicParameterFactory;
import it.free.i.virtual.machine.hal.devicealias.laboratory.simulator.jbullet.VehicleSimulation;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPort;
import it.free.i.virtual.machine.hal.devicealias.port.DeviceAliasPortSupportImpl;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
import java.util.Properties;

import javabullet.ContactAddedCallback;
import javabullet.collision.dispatch.CollisionFlags;
import javabullet.collision.dispatch.CollisionObject;
import javabullet.collision.narrowphase.ManifoldPoint;
import javabullet.dynamics.RigidBody;

import javax.management.Attribute;
import javax.management.AttributeList;
import javax.management.AttributeNotFoundException;
import javax.management.InvalidAttributeValueException;
import javax.management.MBeanException;
import javax.management.MBeanInfo;
import javax.management.Notification;
import javax.management.ReflectionException;

import org.apache.log4j.Logger;
  
/**
 * Dummy port implementation. A device port alias port implementations can receive notifications, have method
 * executed via a jmx call, send notifications about unsolicited read actions that the port want to forward to the
 * controller
 * @author stefano
 */
public class PositioningSystemCollisionDetectorPortImpl extends DeviceAliasPortSupportImpl implements DeviceAliasPort, ContactAddedCallback{

	private long sequenceNumber = 0;
	private Long almostDummyName = 0L;
	//http://caramelleas.blogspot.com/
	//private CollisionHandlerDynamicParameterProxy collisionHandlerDynamicParameterProxyForCubes = null;    //Obstacles: 
	private CollisionHandlerDynamicParameterProxy collisionHandlerDynamicParameterProxyForCapsules = null; //Fuel
	private CollisionHandlerDynamicParameterProxy collisionHandlerDynamicParameterProxyForSphere = null;   //Structure resistence
	private static final String PARAMETER_NAME_FUEL = "Fuel";
	private static final String PARAMETER_NAME_STRUCTURE = "Structure";
	private static Logger logger = Logger.getLogger(PositioningSystemCollisionDetectorPortImpl.class);
	private static int collisionCounter = 1;
	private static final String DUMMY_NAME = "devicealias.port.positioningsystem.collisiondetector";
 
	private Hashtable<String, List<CollisionHandlerDynamicParameterProxy>> parameterUpdateProxies = null;
	
	//private RigidBody myChassis = null;
	
	/*
	public PositioningSystemCollisionDetectorPortImpl(RigidBody chassis){
		almostDummyName = System.currentTimeMillis();
		this.myChassis = chassis;
	}
	*/
	
	/**
	 * Create the dynamic parameters
	 */
	public PositioningSystemCollisionDetectorPortImpl(){
		almostDummyName = System.currentTimeMillis();
		
		parameterUpdateProxies = new Hashtable<String, List<CollisionHandlerDynamicParameterProxy>>();
		
		//Load known shapes: this could be done at API level
		parameterUpdateProxies.put("CapsuleShape", new ArrayList<CollisionHandlerDynamicParameterProxy>());
		parameterUpdateProxies.put("SPHERE", new ArrayList<CollisionHandlerDynamicParameterProxy>());
		
		//TO_DO: use scan of LDAP to find dynamic parameters to link, as now every dynamic parameter 
		// JNDI config has the name of the shape to react with in its JNDI configuration
		
		//collisionHandlerDynamicParameterProxyForCubes = new CollisionHandlerDynamicParameterProxy();
		//Fuel
		collisionHandlerDynamicParameterProxyForCapsules = new CollisionHandlerDynamicParameterProxy();

		//Structure
		collisionHandlerDynamicParameterProxyForSphere = new CollisionHandlerDynamicParameterProxy();
		
		try{
			DynamicParameterFactory.createObject(PARAMETER_NAME_FUEL, 
												 collisionHandlerDynamicParameterProxyForCapsules);			
		}catch(Exception exc) {
			   logger.error("Dynamic virtual parameter [" + PARAMETER_NAME_FUEL + "] will not be started: ", exc);
		}
		
		try{
			DynamicParameterFactory.createObject(PARAMETER_NAME_STRUCTURE,
												 collisionHandlerDynamicParameterProxyForSphere);	
		}catch(Exception exc) {
			   logger.error("Dynamic virtual parameter [" + PARAMETER_NAME_STRUCTURE + "] will not be started: ", exc);
		}
		
	}
	
	public boolean invoke(ManifoldPoint cp, CollisionObject colObj0, int partId0, int index0, CollisionObject colObj1, int partId1, int index1) {
		
		float friction0 = colObj0.getFriction();
		float friction1 = colObj1.getFriction();
		float restitution0 = colObj0.getRestitution();
		float restitution1 = colObj1.getRestitution();

		//RigidBody rigidBody0 = (RigidBody) colObj0;
		RigidBody rigidBody1 = (RigidBody) colObj1;
		
		//logger.info("CollisionEcho detected " + collisionCounter + ", + obj 0: " + colObj0.getCollisionShape().getName() + " obj id: "  + colObj0.getBroadphaseHandle().getUid());
		//logger.info("CollisionEcho detected " + collisionCounter + ", obj 1: " + colObj1.getCollisionShape().getName()  + " obj id: " + colObj1.getBroadphaseHandle().getUid());
		
		//colOb1 is the object throw by the user GUI
		
		collisionCounter++;
		
		if ((colObj0.getCollisionFlags() & CollisionFlags.CUSTOM_MATERIAL_CALLBACK) != 0) {
			friction0 = 1f; //partId0,index0
			restitution0 = 0f;
		}
		if ((colObj1.getCollisionFlags() & CollisionFlags.CUSTOM_MATERIAL_CALLBACK) != 0) {
			if ((index1 & 1) != 0) {
				friction1 = 1f; //partId1,index1
			}
			else {
				friction1 = 0f;
			}
			restitution1 = 0f;
		}

		cp.combinedFriction = VehicleSimulation.calculateCombinedFriction(friction0, friction1);
		cp.combinedRestitution = VehicleSimulation.calculateCombinedRestitution(restitution0, restitution1);

		float collisionX = rigidBody1.getCenterOfMassPosition().x;
		float collisionY = rigidBody1.getCenterOfMassPosition().y;
		float collisionZ = rigidBody1.getCenterOfMassPosition().z;
		
		String collidedShapeName = colObj1.getCollisionShape().getName();	
			
		//TO-DO: notify dynamic parameters
		
		CollisionEcho collisionEcho = PositioningSystemDomainObjectFactory.buildCollision(collisionX, 
														   						  		  collisionY, 
														   						  		  collisionZ, 
														   						  		  collidedShapeName);
		
		Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.PORT_INTERRUPT , 
													        this.getName(), 
													        sequenceNumber++,
													        collisionEcho);
		sendNotification(notification);
	    //logger.info("--> Impact INTERRUPT sent: [" + collisionEcho + "]");
		// this return value is currently ignored, but to be on the safe side: return false if you don't calculate friction
		
	    //Fetch the parameters registered to see which one has to be notified about this collision
	    
//	    List<CollisionHandlerDynamicParameterProxy> proxiesListPerShape = null; 
	    
//	    while(this.parameterUpdateProxies.keys().hasMoreElements()){
//	    	  
//	    	  proxiesListPerShape = this.parameterUpdateProxies.get(this.parameterUpdateProxies.keys().nextElement());
//	    	
//	    	  for(CollisionHandlerDynamicParameterProxy collisionHandlerDynamicParameterProxy: proxiesListPerShape){
//	    		  collisionHandlerDynamicParameterProxy.notifyCollision();
//	    	  }
//	    	  
	    	
//	    }
	    //Do switch on type of dynamic parameter
	    if(collidedShapeName.equals("CapsuleShape")){
	       collisionHandlerDynamicParameterProxyForCapsules.notifyCollision();
	    }else if(collidedShapeName.equals("SPHERE")){
	    	     collisionHandlerDynamicParameterProxyForSphere.notifyCollision();
	    }
	    
	    return true;
	}

	public DeviceAliasPortCommandVerb getMode() {
		return DeviceAliasPortCommandVerb.PORT_DO_READ;
	}

	public String getName() {
		return DUMMY_NAME + "-" + almostDummyName;
	}

	public Object invoke(List<Object> input) {
		//logger.info("# device alias port [" + this.getName() + "] [invoke] call");
		return null;
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void reset() throws DeviceAliasRuntimeException {
		
		logger.info("#Device alias reset invoked");
	}

	public void setMode(DeviceAliasPortCommandVerb mode) {
	}

	public void link(Properties virtualDeviceConfiguration) throws LinkingOperationException {
		logger.info("# device alias port [" + this.getName() + "] [link] call");
	}

	public void unlink() throws LinkingOperationException {
		logger.info("# device alias port [" + this.getName() + "] [unlink] call");
	}
	
	/**
	 * @see it.free.i.virtual.machine.hal.devicealias.DeviceAlias
	 */
	public void handleNotification(Notification notification, Object handback) {
		
		boolean isValid = DeviceAliasFacilities.isPortCommandValid(notification);
		
		if(!isValid){
		   logger.error("Notification received is not valid");
		}
		
		//logger.info("# device alias port [" + this.getName() + "] [command received], now digesting");
		DeviceAliasPortCommand deviceAliasPortCommand = (DeviceAliasPortCommand) notification.getUserData();
		//Execute the command switch case and then decide how to act
		digestCommand(deviceAliasPortCommand);
		
	}
	
	/**
	 * Process the command received
	 * @param command is the device alias command to digest
	 */
	private void digestCommand(DeviceAliasPortCommand command){
	
		
		DeviceAliasPortCommandVerb verb = command.getVerb();
		
		//logger.info("Device alias [" + this.getName() + "] COMMAND VERB RECEIVED: " + verb);
		
		if(DeviceAliasPortCommandVerb.PORT_DO_READ == verb){
		   //logger.info("<-- COLLISION READ Request not implemented for interrupt generators");
		   
			
//			float collisionX = rigidBody1.getCenterOfMassPosition().x;
//			float collisionY = rigidBody1.getCenterOfMassPosition().y;
//			float collisionZ = rigidBody1.getCenterOfMassPosition().z;
//			
//			String collidedShapeName = colObj1.getCollisionShape().getName();	
//				
//			//TO-DO: notify dynamic parameters
//			
//			CollisionEcho collisionEcho = PositioningSystemDomainObjectFactory.buildCollision(collisionX, 
//															   						  		  collisionY, 
//															   						  		  collisionZ, 
//															   						  		  collidedShapeName);
//			
//			Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.PORT_DO_READ_ANSWER, 
//					 										    this.getName(), 
//					 										    sequenceNumber++,
//					 										    readValue);

//					sendNotification(notification);
		   //logger.info("--> COLLISION PING Echo sent");
		   
		}else if(DeviceAliasPortCommandVerb.PORT_PING == verb){
				 //logger.info("<-- COLLISION PING Request received");
			
			     PingEcho pingEcho = new PingEchoImpl();
			     pingEcho.setEchoValue(1);
				
				 Notification notification = super.buildNotification(DeviceAliasPortCommandVerb.PORT_PING_ECHO , 
					        										 this.getName(), 
					        										 sequenceNumber++,
					        										 pingEcho);
				 
				 sendNotification(notification);
				 //logger.info("--> COLLISION PING Echo sent");
		}
		
	}



	@Override
	public Object getAttribute(String attribute)
			throws AttributeNotFoundException, MBeanException,
			ReflectionException {
		return null;
	}



	@Override
	public AttributeList getAttributes(String[] attributes) {
		return null;
	}



	@Override
	public MBeanInfo getMBeanInfo() {
		return JmxFacilities.buildMBeanInfo(this.getClass(), this.getName());
	}



	@Override
	public Object invoke(String actionName, 
						 Object[] params, 
						 String[] signature) throws MBeanException, ReflectionException {
			
		Object invocationReturnValue = null;
	    
		try{
            Method method = this.getClass().getMethod(actionName, new Class[params.length]);
            invocationReturnValue = method.invoke(this, params);
            logger.info("Invoked action [" + actionName + "] invoked method [" + method.getName() + "]");
        }catch(NoSuchMethodException nsmExc) {    
               throw (new MBeanException(nsmExc));
        }catch(SecurityException sExc) {
               throw (new MBeanException(sExc));
        }catch(IllegalArgumentException iaExc) {
               throw (new MBeanException(iaExc));
        }catch(IllegalAccessException iaExc) {
               throw (new MBeanException(iaExc));    
        }catch(InvocationTargetException itExc) {
               throw (new MBeanException(itExc));
        }
        
        return invocationReturnValue;
	}



	@Override
	public void run() {
		
			/*
			try{
				for(int i=0; i < MAX_DEMO_LOOPS; i++){
					Thread.sleep(3000);
					DeviceAliasPortMessage deviceAliasPortMessage = new DeviceAliasPortMessageImpl();
					Notification notification = buildNotification(this, AttributeChangeNotification.ATTRIBUTE_CHANGE, this.getName() + " notification", deviceAliasPortMessage);
					sendNotification(notification);
				}
			}catch(InterruptedException e) {
			  	
			}
			*/
	}



	@Override
	public void setAttribute(Attribute attribute)
			throws AttributeNotFoundException, InvalidAttributeValueException,
			MBeanException, ReflectionException {
	}

	@Override
	public AttributeList setAttributes(AttributeList attributes) {		
		return null;
	}
	
	/**
	 * Build the notification object with custom params for this application subsystem domain
	 * @param type is the type of notification sent
	 * @param message is a message associated with this notification
	 * @param payload is the data bundle to send back
	 * @return a notification ready-to-send object
	 */
//	private Notification buildNotification(String type, String message, Object payload){
//		
//		DeviceAliasPortMessage deviceAliasPortMessage = new DeviceAliasPortMessageImpl();
//		
//		Notification notification = new Notification(type,
//                									 this,
//                									 sequenceNumber,
//                									 System.currentTimeMillis(),
//                									 this.getName() + " " + message);
//		
//		deviceAliasPortMessage.setPayload(payload);
//		
//		notification.setUserData(deviceAliasPortMessage);
//		
//		return notification;
//	}
	
}
